﻿using System;
using System.Text;
using System.Threading;
using ApplicationInfrastructure;
using TI_BLE_HCI_ClientLib.HCIBaseEntities;
using TI_BLE_HCI_ClientLib.HCICommands;
using TI_BLE_HCI_ClientLib.HCIEvents;

namespace TI_BLE_HCI_ClientLib.Controllers
{
    public class HCISimpleSerialCentralProfileController : HCIController
    {
#if DEBUG
        private const int COMMAND_TIMEOUT_MSECS = 60000;
#else
        const int COMMAND_TIMEOUT_MSECS = 5000;
#endif
        private const ushort INVALID_CONNECTION_HANDLE = 0xffff;
        private const byte INVALID_BYTE_VALUE = 0xff;
        private const ushort INVALID_USHORT_VALUE = 0xffff;

        public HCISimpleSerialCentralProfileController(string ComPort, int BaudRate)
            : base(DependencyResolver.GetFrameManager(DependencyResolver.GetSerialPortFullDuplexChannel(ComPort, BaudRate)))
        {
            HCIEventReceived += HCISimpleSerialController_HCIEventReceived;
        }

        private void HCISimpleSerialController_HCIEventReceived(HCIEvent hciEvent)
        {
            if (hciEvent is HCIEvent_ATT_Handle_Value_Notification)
            {
                var hciEvent_ATT_HandleNotification = hciEvent as HCIEvent_ATT_Handle_Value_Notification;

                if (OnNotification != null) OnNotification(hciEvent_ATT_HandleNotification);

                return;
            }

            if (hciEvent is HCIEvent_ATT_Handle_Value_Indication)
            {
                var hciEvent_ATT_HandleIndication = hciEvent as HCIEvent_ATT_Handle_Value_Indication;

                if (OnIndication != null) OnIndication(hciEvent_ATT_HandleIndication);

                return;
            }

            if (hciEvent is HCIEvent_GAP_Device_Information)
            {
                var hciEvent_GAP_DeviceInformation = hciEvent as HCIEvent_GAP_Device_Information;

                if (OnDeviceInformation != null) OnDeviceInformation(hciEvent_GAP_DeviceInformation);

                return;
            }

            if (hciEvent is HCIEvent_GAP_Device_Discovery_Done)
            {
                var hciEvent_GAP_DeviceDiscoveryDone = hciEvent as HCIEvent_GAP_Device_Discovery_Done;

                if (OnDeviceDiscoveryDone != null) OnDeviceDiscoveryDone(hciEvent_GAP_DeviceDiscoveryDone);
            }
        }

        public delegate void NotificationHandler(HCIEvent_ATT_Handle_Value_Notification notification);

        public delegate void IndicationHandler(HCIEvent_ATT_Handle_Value_Indication indication);

        public delegate void DeviceInformationHandler(HCIEvent_GAP_Device_Information deviceInformation);

        public delegate void DeviceDiscoveryDoneHandler(HCIEvent_GAP_Device_Discovery_Done deviceDiscoveryDone);

        public event IndicationHandler OnIndication;
        public event NotificationHandler OnNotification;
        public event DeviceInformationHandler OnDeviceInformation;
        public event DeviceDiscoveryDoneHandler OnDeviceDiscoveryDone;

        private enum StateMachineStateEnum
        {
            Idle,
            Phase1,
            Phase2,
            Phase3
        }

        public bool Init(byte maxScanResponses = 5)
        {
            var state = StateMachineStateEnum.Idle;

            HCIEvent_GAP_Device_Init_Done evtDeviceInitDone = null;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            var locker = new object();

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                lock (locker)
                {
                    if (evt is HCIEvent_GAP_Command_Status)
                    {
                        if (state == StateMachineStateEnum.Idle)
                        {
                            if ((evt as HCIEvent_GAP_Command_Status).Status == HCIVendorSpecificEventHostErrorCode.Success)
                            {
                                state = StateMachineStateEnum.Phase1;
                            }
                            else
                            {
                                eventWaitable.Set();
                            }

                            return;
                        }
                    }

                    if (evt is HCIEvent_GAP_Device_Init_Done)
                    {
                        if (state == StateMachineStateEnum.Phase1)
                        {
                            commandWaitable.Reset();
                            evtDeviceInitDone = evt as HCIEvent_GAP_Device_Init_Done;
                            eventWaitable.Set();
                        }
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GAP_DEVICE_INIT
                SendCommand(
                    new HCICommand_GAP_Device_Init(
                        HCICommand_GAP_Device_Init.ProfileRoleEnum.GAPProfileCentral,
                        maxScanResponses,
                        new byte[16],
                        new byte[16],
                        1)
                    );

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                return evtDeviceInitDone != null && evtDeviceInitDone.Status == HCIVendorSpecificEventHostErrorCode.Success;
            }
            finally
            {
#if !(MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public bool BeginDeviceDiscovery()
        {
            HCIEvent_GAP_Command_Status evtCommandStatus = null;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            var locker = new object();

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                lock (locker)
                {
                    if (evt is HCIEvent_GAP_Command_Status)
                    {
                        commandWaitable.Reset();
                        evtCommandStatus = evt as HCIEvent_GAP_Command_Status;
                        eventWaitable.Set();
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GAP_DEVICE_DISCOVERY_REQUEST
                SendCommand(new HCICommand_GAP_Device_Discovery_Request(
                    HCICommand_GAP_Device_Discovery_Request.ModeEnum.ScanForAllDevices,
                    HCICommand_GAP_Device_Discovery_Request.ActiveScanEnum.TurnOnActiveScanning,
                    HCICommand_GAP_Device_Discovery_Request.WhiteListEnum.DontUseWhiteListDuringScanning));

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                return evtCommandStatus != null && evtCommandStatus.Status == HCIVendorSpecificEventHostErrorCode.Success;
            }
            finally
            {
#if !(MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public bool EndDeviceDiscovery()
        {
            HCIEvent_GAP_Command_Status evtCommandStatus = null;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            var locker = new object();

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                lock (locker)
                {
                    if (evt is HCIEvent_GAP_Command_Status)
                    {
                        commandWaitable.Reset();
                        evtCommandStatus = evt as HCIEvent_GAP_Command_Status;
                        eventWaitable.Set();
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GAP_DEVICE_DISCOVERY_CANCEL
                SendCommand(new HCICommand_GAP_Device_Discovery_Cancel());

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                return evtCommandStatus != null && evtCommandStatus.Status == HCIVendorSpecificEventHostErrorCode.Success;
            }
            finally
            {
#if !(MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public void Terminate()
        {
            Stop();

            HCIEventReceived -= HCISimpleSerialController_HCIEventReceived;
        }

        public bool ConnectTo(byte[] address, out ushort connectionHandle)
        {
            if (address == null || address.Length != 6) throw new ArgumentException("Invalid address");

            connectionHandle = INVALID_CONNECTION_HANDLE;

            var state = StateMachineStateEnum.Idle;

            HCIEvent_GAP_Link_Established evtLinkEstablished = null;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            var locker = new object();

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                lock (locker)
                {
                    if (evt is HCIEvent_GAP_Command_Status)
                    {
                        if (state == StateMachineStateEnum.Idle)
                        {
                            if ((evt as HCIEvent_GAP_Command_Status).Status == HCIVendorSpecificEventHostErrorCode.Success)
                            {
                                state = StateMachineStateEnum.Phase1;
                            }
                            else
                            {
                                eventWaitable.Set();
                            }

                            return;
                        }
                    }

                    if (evt is HCIEvent_GAP_Link_Established)
                    {
                        if (state == StateMachineStateEnum.Phase1)
                        {
                            commandWaitable.Reset();
                            evtLinkEstablished = evt as HCIEvent_GAP_Link_Established;
                            eventWaitable.Set();
                        }
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GAP_ESTABLISH_LINK_REQUEST
                SendCommand(
                    new HCICommand_GAP_Establish_Link_Request(
                        HCICommand_GAP_Establish_Link_Request.HighDutyCycleEnum.Disabled,
                        HCICommand_GAP_Establish_Link_Request.WhiteListEnum.DontUseWhiteList,
                        HCICommand_GAP_Establish_Link_Request.AddrTypePeerEnum.AddrType_Public,
                        address)
                    );

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                if (evtLinkEstablished != null && evtLinkEstablished.Status == HCIVendorSpecificEventHostErrorCode.Success)
                {
                    connectionHandle = evtLinkEstablished.ConnHandle;

                    return true;
                }

                return false;
            }
            finally
            {
#if !(MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public bool CloseConnection(ushort connectionHandle)
        {
            var state = StateMachineStateEnum.Idle;

            HCIEvent_GAP_Link_Terminated evtLinkTerminated = null;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            var locker = new object();

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                lock (locker)
                {
                    if (evt is HCIEvent_GAP_Command_Status)
                    {
                        if (state == StateMachineStateEnum.Idle)
                        {
                            if ((evt as HCIEvent_GAP_Command_Status).Status == HCIVendorSpecificEventHostErrorCode.Success)
                            {
                                state = StateMachineStateEnum.Phase1;
                            }
                            else
                            {
                                eventWaitable.Set();
                            }

                            return;
                        }
                    }

                    if (evt is HCIEvent_GAP_Link_Terminated)
                    {
                        if (state == StateMachineStateEnum.Phase1)
                        {
                            var evtReceivedLinkTerminated = evt as HCIEvent_GAP_Link_Terminated;

                            if (evtReceivedLinkTerminated.ConnHandle == connectionHandle)
                            {
                                commandWaitable.Reset();
                                evtLinkTerminated = evtReceivedLinkTerminated;
                                eventWaitable.Set();
                            }
                        }
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GAP_TERMINATE_LINK_REQUEST
                SendCommand(
                    new HCICommand_GAP_Terminate_Link_Request(connectionHandle, HCICommand_GAP_Terminate_Link_Request.ReasonEnum.RemoteUserTerminatedConnection)
                    );

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                return evtLinkTerminated != null && evtLinkTerminated.Status == HCIVendorSpecificEventHostErrorCode.Success;
            }
            finally
            {
#if ! (MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public bool AbortLatestConnectionRequest()
        {
            return CloseConnection(0xFFFE);
        }

        public bool CloseAllConnections()
        {
            return CloseConnection(0xFFFF);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, byte[] value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            var localAttributeErrorCode = AttributeErrorCodeEnum.None;
            attributeErrorCode = localAttributeErrorCode;

            HCIEvent_ATT_Write_Characteristic_Value_Response evtWriteCharValueResponse = null;

            var state = StateMachineStateEnum.Idle;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                if (evt is HCIEvent_GATT_Base_Event)
                {
                    if ((evt as HCIEvent_GATT_Base_Event).ConnHandle != connectionHandle) return;
                }

                if (evt is HCIEvent_ATT_Error_Response)
                {
                    var errorResponse = (evt as HCIEvent_ATT_Error_Response);

                    if (errorResponse.AttributeHandle == characteristicHandle &&
                        errorResponse.RequestOpCode == HCICommand_GATT_Write_Characteristic_Value_Request.CommandOpCode)
                    {
                        localAttributeErrorCode = (AttributeErrorCodeEnum)errorResponse.ErrorCode;

                        eventWaitable.Set();

                        return;
                    }
                }

                if (evt is HCIEvent_GAP_Command_Status)
                {
                    if (state == StateMachineStateEnum.Idle)
                    {
                        if ((evt as HCIEvent_GAP_Command_Status).Status == HCIVendorSpecificEventHostErrorCode.Success)
                        {
                            state = StateMachineStateEnum.Phase1;
                        }
                        else
                        {
                            eventWaitable.Set();
                        }

                        return;
                    }
                }

                if (evt is HCIEvent_ATT_Write_Characteristic_Value_Response)
                {
                    if (state == StateMachineStateEnum.Phase1)
                    {
                        var evtReceivedWriteCharValueResponse = evt as HCIEvent_ATT_Write_Characteristic_Value_Response;

                        if (evtReceivedWriteCharValueResponse.ConnHandle == connectionHandle)
                        {
                            commandWaitable.Reset();
                            evtWriteCharValueResponse = evt as HCIEvent_ATT_Write_Characteristic_Value_Response;
                            eventWaitable.Set();
                        }
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GATT_WRITE_CHARACTERISTIC_VALUE_REQUEST
                SendCommand(
                    new HCICommand_GATT_Write_Characteristic_Value_Request(
                        connectionHandle,
                        characteristicHandle,
                        value)
                    );

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                attributeErrorCode = localAttributeErrorCode;

                return localAttributeErrorCode == AttributeErrorCodeEnum.None && evtWriteCharValueResponse != null && evtWriteCharValueResponse.Status == HCIVendorSpecificEventHostErrorCode.Success;
            }
            finally
            {
#if ! (MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, byte[] value)
        {
            AttributeErrorCodeEnum dummy;

            return WriteCharacteristic(connectionHandle, characteristicHandle, value, out dummy);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, byte value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, new[] { value }, out attributeErrorCode);
        }


        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, byte value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, new[] { value });
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, ushort value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, new[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF) }, out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, ushort value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, new[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF) });
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, string value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, Encoding.UTF8.GetBytes(value), out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, string value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, Encoding.UTF8.GetBytes(value));
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, float value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value), out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, float value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, double value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value), out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, double value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, int value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value), out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, int value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, uint value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value), out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, uint value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, short value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value), out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, short value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, bool value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, value ? (ushort)0x0001 : (ushort)0x0000, out attributeErrorCode);
        }

        public bool WriteCharacteristic(ushort connectionHandle, ushort characteristicHandle, bool value)
        {
            return WriteCharacteristic(connectionHandle, characteristicHandle, value ? (ushort)0x0001 : (ushort)0x0000);
        }

        public bool ReadCharacteristic(ushort connectionHandle, ushort characteristicHandle, out byte[] value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            var localAttributeErrorCode = AttributeErrorCodeEnum.None;
            attributeErrorCode = localAttributeErrorCode;

            HCIEvent_ATT_Read_Characteristic_Value_Response evtReadCharValueResponse = null;

            value = null;

            var state = StateMachineStateEnum.Idle;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                if (evt is HCIEvent_GATT_Base_Event)
                {
                    if ((evt as HCIEvent_GATT_Base_Event).ConnHandle != connectionHandle) return;
                }

                if (evt is HCIEvent_ATT_Error_Response)
                {
                    var errorResponse = (evt as HCIEvent_ATT_Error_Response);

                    if (errorResponse.AttributeHandle == characteristicHandle &&
                        errorResponse.RequestOpCode == HCICommand_GATT_Read_Characteristic_Value_Request.CommandOpCode)
                    {
                        localAttributeErrorCode = (AttributeErrorCodeEnum)errorResponse.ErrorCode;

                        eventWaitable.Set();

                        return;
                    }
                }

                if (evt is HCIEvent_GAP_Command_Status)
                {
                    if (state == StateMachineStateEnum.Idle)
                    {
                        if ((evt as HCIEvent_GAP_Command_Status).Status == HCIVendorSpecificEventHostErrorCode.Success)
                        {
                            state = StateMachineStateEnum.Phase1;
                        }
                        else
                        {
                            eventWaitable.Set();
                        }

                        return;
                    }
                }

                if (evt is HCIEvent_ATT_Read_Characteristic_Value_Response)
                {
                    if (state == StateMachineStateEnum.Phase1)
                    {
                        var evtReceivedReadCharValueResponse = evt as HCIEvent_ATT_Read_Characteristic_Value_Response;

                        if (evtReceivedReadCharValueResponse.ConnHandle == connectionHandle)
                        {
                            commandWaitable.Reset();
                            evtReadCharValueResponse = evt as HCIEvent_ATT_Read_Characteristic_Value_Response;
                            eventWaitable.Set();
                        }
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GATT_READ_CHARACTERISTIC_VALUE_REQUEST
                SendCommand(
                    new HCICommand_GATT_Read_Characteristic_Value_Request(
                        connectionHandle,
                        characteristicHandle)
                    );

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                attributeErrorCode = localAttributeErrorCode;

                if (localAttributeErrorCode == AttributeErrorCodeEnum.None && evtReadCharValueResponse != null && evtReadCharValueResponse.Status == HCIVendorSpecificEventHostErrorCode.Success)
                {
                    value = evtReadCharValueResponse.Value;

                    return true;
                }

                return false;
            }
            finally
            {
#if !(MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }

        public bool ReadCharacteristic(ushort connectionHandle, ushort characteristicHandle, out byte[] value)
        {
            var dummy = AttributeErrorCodeEnum.None;

            return ReadCharacteristic(connectionHandle, characteristicHandle, out value, out dummy);
        }

        public bool ReadCharacteristicAsUInt8(ushort connectionHandle, ushort characteristicHandle, out byte value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0x00;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok || outVal.Length != 1) return false;

            value = outVal[0];

            return true;
        }


        public bool ReadCharacteristicAsUInt8(ushort connectionHandle, ushort characteristicHandle, out byte value)
        {
            byte[] outVal = null;
            value = 0x00;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok || outVal.Length != 1) return false;

            value = outVal[0];

            return true;
        }

        public bool ReadCharacteristicAsUInt16(ushort connectionHandle, ushort characteristicHandle, out ushort value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0x0000;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok || outVal.Length != 2) return false;

            value = (ushort)(outVal[0] + (outVal[1] << 8));

            return true;
        }

        public bool ReadCharacteristicAsUInt16(ushort connectionHandle, ushort characteristicHandle, out ushort value)
        {
            byte[] outVal = null;
            value = 0x0000;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok || outVal.Length != 2) return false;

            value = (ushort)(outVal[0] + (outVal[1] << 8));

            return true;
        }

        public bool ReadCharacteristicAsBoolean(ushort connectionHandle, ushort characteristicHandle, out bool value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = false;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok || outVal.Length != 2) return false;

            value = ((ushort)(outVal[0] + (outVal[1] << 8))) != 0x0000;

            return true;
        }

        public bool ReadCharacteristicAsBoolean(ushort connectionHandle, ushort characteristicHandle, out bool value)
        {
            byte[] outVal = null;
            value = false;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok || outVal.Length != 2) return false;

            value = ((ushort)(outVal[0] + (outVal[1] << 8))) != 0x0000;

            return true;
        }

        public bool ReadCharacteristicAsString(ushort connectionHandle, ushort characteristicHandle, out string value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = null;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok) return false;

            if (outVal == null) return true;

            if (outVal.Length == 0)
            {
                value = string.Empty;
                return true;
            }

            value = new string(Encoding.UTF8.GetChars(outVal));

            return true;
        }

        public bool ReadCharacteristicAsString(ushort connectionHandle, ushort characteristicHandle, out string value)
        {
            byte[] outVal = null;
            value = null;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok) return false;

            if (outVal == null) return true;

            if (outVal.Length == 0)
            {
                value = string.Empty;
                return true;
            }

            value = new string(Encoding.UTF8.GetChars(outVal));

            return true;
        }

        public bool ReadCharacteristicAsInt32(ushort connectionHandle, ushort characteristicHandle, out int value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToInt32(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsInt32(ushort connectionHandle, ushort characteristicHandle, out int value)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToInt32(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsUInt32(ushort connectionHandle, ushort characteristicHandle, out uint value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToUInt32(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsUInt32(ushort connectionHandle, ushort characteristicHandle, out uint value)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToUInt32(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsInt16(ushort connectionHandle, ushort characteristicHandle, out short value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToInt16(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsInt16(ushort connectionHandle, ushort characteristicHandle, out short value)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToInt16(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsSingle(ushort connectionHandle, ushort characteristicHandle, out float value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToSingle(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsSingle(ushort connectionHandle, ushort characteristicHandle, out float value)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToSingle(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsDouble(ushort connectionHandle, ushort characteristicHandle, out double value, out AttributeErrorCodeEnum attributeErrorCode)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal, out attributeErrorCode);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToDouble(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool ReadCharacteristicAsDouble(ushort connectionHandle, ushort characteristicHandle, out double value)
        {
            byte[] outVal = null;
            value = 0;

            var ok = ReadCharacteristic(connectionHandle, characteristicHandle, out outVal);

            if (!ok) return false;

            try
            {
                value = BitConverter.ToDouble(outVal, 0);

                return true;
            }
            catch
            {
                return false;
            }
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, byte[] value)
        {
            // INVIO COMANDO GATT_WRITE_CHARACTERISTIC_VALUE_REQUEST
            SendCommandAsync(
                new HCICommand_GATT_Write_Characteristic_Value_Request(
                    connectionHandle,
                    characteristicHandle,
                    value)
                );
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, byte value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, new[] { value });
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, ushort value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, new[] { (byte)(value & 0xFF), (byte)((value >> 8) & 0xFF) });
        }


        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, string value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, Encoding.UTF8.GetBytes(value));
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, bool value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, value ? (ushort)0x0001 : (ushort)0x0000);
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, int value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, uint value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, short value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, float value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public void WriteCharacteristicAsync(ushort connectionHandle, ushort characteristicHandle, double value)
        {
            WriteCharacteristicAsync(connectionHandle, characteristicHandle, BitConverter.GetBytes(value));
        }

        public bool SendIndicationConfirmation(ushort connectionHandle)
        {
            HCIEvent_GAP_Command_Status evtCommandStatus = null;

            var commandWaitable = new ManualResetEvent(false);
            var eventWaitable = new AutoResetEvent(false);

            var locker = new object();

            HCIEventHandler handler = (evt =>
            {
                commandWaitable.WaitOne();

                lock (locker)
                {
                    if (evt is HCIEvent_GAP_Command_Status)
                    {
                        commandWaitable.Reset();
                        evtCommandStatus = evt as HCIEvent_GAP_Command_Status;
                        eventWaitable.Set();
                    }
                }
            });

            try
            {
                HCIEventReceived += handler;

                // INVIO COMANDO GATT_HANDLE_INDICATION_CONFIRMATION
                SendCommand(new HCICommand_GATT_Handle_Value_Indication_Confirmation(connectionHandle));

                commandWaitable.Set();

                var ok = eventWaitable.WaitOne(COMMAND_TIMEOUT_MSECS, false);

                if (!ok) return false;

                return evtCommandStatus != null && evtCommandStatus.Status == HCIVendorSpecificEventHostErrorCode.Success;
            }
            finally
            {
#if !(MF_FRAMEWORK_VERSION_V4_3 || MF_FRAMEWORK_VERSION_V4_2)
                commandWaitable.Dispose();
                eventWaitable.Dispose();
#endif
                HCIEventReceived -= handler;
            }
        }
    }

    public enum AttributeErrorCodeEnum : byte
    {
        None = 0x00, //!< None
        AttErrInvalidHandle = 0x01, //!< Attribute handle value given was not valid on this attribute server
        AttErrReadNotPermitted = 0x02, //!< Attribute cannot be read
        AttErrWriteNotPermitted = 0x03, //!< Attribute cannot be written
        AttErrInvalidPdu = 0x04, //!< The attribute PDU was invalid
        AttErrInsufficientAuthen = 0x05, //!< The attribute requires authentication before it can be read or written
        AttErrUnsupportedReq = 0x06, //!< Attribute server doesn't support the request received from the attribute client
        AttErrInvalidOffset = 0x07, //!< Offset specified was past the end of the attribute
        AttErrInsufficientAuthor = 0x08, //!< The attribute requires an authorization before it can be read or written
        AttErrPrepareQueueFull = 0x09, //!< Too many prepare writes have been queued
        AttErrAttrNotFound = 0x0a, //!< No attribute found within the given attribute handle range
        AttErrAttrNotLong = 0x0b, //!< Attribute cannot be read or written using the Read Blob Request or Prepare Write Request
        AttErrInsufficientKeySize = 0x0c, //!< The Encryption Key Size used for encrypting this link is insufficient
        AttErrInvalidValueSize = 0x0d, //!< The attribute value length is invalid for the operation
        AttErrUnlikely = 0x0e, //!< The attribute request that was requested has encountered an error that was very unlikely, and therefore could not be completed as requested
        AttErrInsufficientEncrypt = 0x0f, //!< The attribute requires encryption before it can be read or written
        AttErrUnsupportedGrpType = 0x10, //!< The attribute type is not a supported grouping attribute as defined by a higher layer specification
        AttErrInsufficientResources = 0x11, //!< Insufficient Resources to complete the request
        AttErrInvalidValue = 0x80, //!< The attribute value is invalid for the operation
    }
}